﻿// This file is part of Code4Public (http://code4public.codeplex.com)
// Copyright 2011 Sina Iravanian - <sina@sinairv.com>
//
// This source file(s) may be redistributed, altered and customized
// by any means PROVIDING the authors name and all copyright
// notices remain intact.
// THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED. USE IT AT YOUR OWN RISK. THE AUTHOR ACCEPTS NO
// LIABILITY FOR ANY DATA DAMAGE/LOSS THAT THIS PRODUCT MAY CAUSE.
// ------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;
using Code4Public.Settings;
using System.Diagnostics;
using Code4Public.Utils;
using System.Drawing;
using Code4Public.Languages;
using Code4Public.Parsing;
using System.Windows.Forms;
using Code4Public.GUI;

namespace Code4Public
{
    public class HtmlGenerator
    {
        #region Private Fields

        /// <summary>
        /// reference to the program logic core.
        /// </summary>
        private Core core;

        /// <summary>
        /// reference to the currently selected theme.
        /// </summary>
        private Theme theme;

        /// <summary>
        /// the number of digits used to show the largest line number of the current code.
        /// </summary>
        private int linesDigitCount;

        /// <summary>
        /// Dictionary from main elements of theme to their style stirng
        /// </summary>
        private Dictionary<string, string> dicStyles = new Dictionary<string, string>();
        
        /// <summary>
        /// reference to the rich text-box html produced in Copy-Paste Mode.
        /// this variable is necessary because the related method 
        /// is called through a void delegate.
        /// </summary>
        private string richTextBoxHtml = "";

        /// <summary>
        /// Reference to the rich-text box containing the code.
        /// </summary>
        private RichTextBox richTextBox = null;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="HtmlGennerator"/> class.
        /// Setting the reference to the core, and initializing some properties.
        /// </summary>
        /// <param name="core">The core.</param>
        public HtmlGenerator(Core core)
        {
            this.core = core;

            this.HeaderCaption = "";
            this.StyleCode = "";
            this.HTMLBodyCode = "";
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the style code.
        /// </summary>
        /// <value>The style code.</value>
        public string StyleCode { get; private set; }

        /// <summary>
        /// Gets the HTML body code.
        /// </summary>
        /// <value>The HTML body code.</value>
        public string HTMLBodyCode { get; private set; }

        /// <summary>
        /// Gets or sets a value indicating whether the header should be shown.
        /// </summary>
        /// <value><c>true</c> if the header should be shown; otherwise, <c>false</c>.</value>
        public bool ShowHeader { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the styles should be embeded.
        /// </summary>
        /// <value><c>true</c> if the styles should be embeded; otherwise, <c>false</c>.</value>
        public bool EmbedStyles { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the highlighting should be enabled for output.
        /// </summary>
        /// <value><c>true</c> if the highlighting should be enabled for output; otherwise, <c>false</c>.</value>
        public bool EnableHighlighting { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the line numbers should be added.
        /// </summary>
        /// <value><c>true</c> if the line numbers should be added; otherwise, <c>false</c>.</value>
        public bool AddLineNo { get; set; }

        /// <summary>
        /// Gets or sets the header caption.
        /// </summary>
        /// <value>The header caption.</value>
        public string HeaderCaption { get; set; }

        #endregion

        #region HTML Generation Callers

        /// <summary>
        /// Generates the HTML from the rich-text box.
        /// </summary>
        /// <param name="textBox">The text box.</param>
        public void GenerateHTML(RichTextBox textBox)
        {
            StyleCode = "";
            HTMLBodyCode = "";

            theme = core.Themes.SelectedTheme;
            if (theme == null)
            {
                theme = new Theme(""); // an instance with default values
            }

            // if there's no code, then nothing is needed to be generated
            if (textBox.TextLength <= 0)
                return;

            if (this.HeaderCaption == null)
            {
                if (core.Languages.IsAnyLanguageSelected)
                    this.HeaderCaption = core.Languages.SelectedLanguageName;
                else
                    this.HeaderCaption = CodeConstants.GUI.CopyPasteModeCaption;
            }

            if (this.HeaderCaption.Length <= 0)
                this.ShowHeader = false;

            if (!core.Languages.IsAnyLanguageSelected)
            {
                GenerateHTMLFromRTF(textBox);
            }
            else
            {
                GenerateHTMLFromLanguage(textBox.Text, textBox.Lines.Length);
            }

        }

        /// <summary>
        /// The base method that generates html from the RTF of the text-box.
        /// This method sets the value of richTextBoxHtml variable (since it will 
        /// be called through a delegate).
        /// </summary>
        private void GenerateHTMLFromRTFBase()
        {
            richTextBoxHtml = "";
            if (richTextBox.TextLength <= 0) return;

            StringBuilder sbHTMLResult = new StringBuilder();
//            bool useHighlighting = this.EnableHighlighting;
            int maxLineNo = richTextBox.Lines.Length;
            int textLen = richTextBox.TextLength;

            StringBuilder sb = new StringBuilder();

            int curLineNo = 1;
            Color curColor = Color.Black;
            bool curBold = false;
            bool curItalic = false;

            for (int i = 0; i < textLen; ++i)
            {
                richTextBox.Select(i, 1);
                string sel = richTextBox.SelectedText;
                if ((this.EnableHighlighting) &&
                    ((curColor != richTextBox.SelectionColor) || (curBold != richTextBox.SelectionFont.Bold) || (curItalic != richTextBox.SelectionFont.Italic)))
                {
                    string content = sb.ToString();
                    if (content.Length > 0)
                    {
                        sbHTMLResult.Append(GenerateEmbeddedStyleSpan(content, curColor, curBold, curItalic));
                        sb = new StringBuilder();
                    }

                    curColor = richTextBox.SelectionColor;
                    curBold = richTextBox.SelectionFont.Bold;
                    curItalic = richTextBox.SelectionFont.Italic;
                }

                if (sel == "\n")
                {
                    if (this.EnableHighlighting)
                    {
                        sbHTMLResult.Append(GenerateEmbeddedStyleSpan(sb.ToString(), curColor, curBold, curItalic));
                    }
                    else
                    {
                        if (this.EmbedStyles)
                        {
                            sbHTMLResult.Append(GenerateEmbeddedStyleSpan(sb.ToString(), theme.CodeContentFont.Color, theme.CodeContentFont.Bold, theme.CodeContentFont.Italic));
                        }
                        else
                        {
                            sbHTMLResult.Append(GenerateNotEmbeddedStyleSpan(sb.ToString(), CodeConstants.HTML.StyleNamePrefix + CodeConstants.HTML.StyleNameCodeDefaultColor));
                        }
                    }

                    sb = new StringBuilder();
                    sbHTMLResult.AppendLine();
                    curLineNo++;
                }
                else if (sel == "\t")
                {
                    if (AppSettings.Settings.ReplaceTabs)
                    {
                        for (int j = 0; j < AppSettings.Settings.TabSize; ++j)
                        {
                            sb.Append(" ");
                        }
                    }
                    else
                    {
                        sb.Append("\t");
                    }
                }
                else
                {
                    sb.Append(sel);
                }
            }

            // finally add the remaining text in the sb which was not yet flushed
            string remContent = sb.ToString();
            sbHTMLResult.Append(GenerateEmbeddedStyleSpan(remContent, curColor, curBold, curItalic));

            richTextBoxHtml = sbHTMLResult.ToString();
        }

        /// <summary>
        /// Generates the HTML from RTF.
        /// </summary>
        /// <param name="textBox">The text box.</param>
        private void GenerateHTMLFromRTF(RichTextBox textBox)
        {
            Debug.Assert(textBox != null);

            richTextBoxHtml = "";
            richTextBox = textBox;

            TextBoxUtils.DisableAndDoAndEnable(textBox, GenerateHTMLFromRTFBase);

            string codeBody = String.Format("{1}<pre>{0}{1}</pre>", richTextBoxHtml, Environment.NewLine);
            GenerateHTMLBody(codeBody, textBox.Lines.Length);
        }

        /// <summary>
        /// Generates the HTML from the currently selected language.
        /// </summary>
        /// <param name="code">The code.</param>
        /// <param name="lineCount">The line count.</param>
        private void GenerateHTMLFromLanguage(string code, int lineCount)
        {
            LanguageInfo selectedLanguage = core.Languages.SelectedLanguage;
            
            // because this should have been checked before
            if (selectedLanguage == null)
                Debug.Assert(false);

            // create tab replacement string once forever
            string tabReplacement = "\t";
            if (AppSettings.Settings.ReplaceTabs)
            {
                StringBuilder sbTab = new StringBuilder();
                for (int i = 0; i < AppSettings.Settings.TabSize; ++i)
                {
                    sbTab.Append(" ");
                }
                tabReplacement = sbTab.ToString();
            }

            ParserHelper helper = new ParserHelper(selectedLanguage);
            StringBuilder sb = new StringBuilder();
            sb.AppendLine();
            sb.Append("<pre>");

            foreach (var exp in helper.GetExpressions(code))
            {
                if (exp.Type == ExpressionType.Whitespace)
                {
                    string wsContent = exp.Content;
                    if (AppSettings.Settings.ReplaceTabs && wsContent.IndexOf("\t") >= 0)
                    {
                        wsContent = wsContent.Replace("\t", tabReplacement);
                    }
                    sb.Append(wsContent);
                }
                else if (exp.Type == ExpressionType.Newline)
                {
                    sb.AppendLine();
                }
                else
                {
                    string grpName = Core.MapExpressionToColorProfileGroupName(exp);
                    if (!EnableHighlighting || (!core.ColorProfiles.IsAnyProfileSelected) || grpName.Length <= 0)
                    {
                        if (this.EmbedStyles)
                        {
                            sb.Append(GenerateEmbeddedStyleSpan(exp.Content, 
                                theme.CodeContentFont.Color, theme.CodeContentFont.Bold, theme.CodeContentFont.Italic));
                        }
                        else
                        {
                            sb.Append(GenerateNotEmbeddedStyleSpan(exp.Content, 
                                CodeConstants.HTML.StyleNamePrefix + CodeConstants.HTML.StyleNameCodeDefaultColor));
                        }
                    }
                    else
                    {
                        ColorProfile.GroupProperties props;
                        core.ColorProfiles.SelectedProfile.TryGetProperties(grpName, out props);
                        if (props != null)
                        {
                            if (EmbedStyles)
                            {
                                sb.Append(GenerateEmbeddedStyleSpan(exp.Content, 
                                    props.Color, props.Bold, props.Italic
                                ));
                            }
                            else
                            {
                                sb.Append(GenerateNotEmbeddedStyleSpan(exp.Content,
                                    CodeConstants.HTML.StyleNameCodeColorPrefix + grpName
                                ));
                            }
                        }
                        else
                        {
                            if (this.EmbedStyles)
                            {
                                sb.Append(GenerateEmbeddedStyleSpan(exp.Content, 
                                    theme.CodeContentFont.Color, theme.CodeContentFont.Bold, theme.CodeContentFont.Italic));
                            }
                            else
                            {
                                sb.Append(GenerateNotEmbeddedStyleSpan(exp.Content, 
                                    CodeConstants.HTML.StyleNamePrefix + CodeConstants.HTML.StyleNameCodeDefaultColor));
                            }
                        }
                    }
                }
            }

            sb.AppendLine();
            sb.Append("</pre>");

            GenerateHTMLBody(sb.ToString(), lineCount);
        }

        #endregion

        #region HTML Generation Details

        /// <summary>
        /// Sets StyleCode and HTMLBodyCode properties
        /// </summary>
        private void GenerateHTMLBody(string codeContent, int lineCount)
        {
            GenerateThemeBasedStyles();

            linesDigitCount = (int)Math.Floor(Math.Log10(lineCount)) + 1;

            string widthString = "100%";
            if (RunTimeParams.MaxWidth > 0)
                widthString = RunTimeParams.MaxWidth.ToString() + "px";


            string divStyle = CreateStyleString(
                "width", widthString,
                "max-width", widthString,
                "direction", "ltr",
                "text-align", "left",
                "margin-top", "8px"
            );

           XElement htmlElem = new XElement("div",
                new XAttribute("style", divStyle),
                (ShowHeader ? GetHeaderDiv() : null),
                GetBodyDiv()
            );

            string htmlTemplate = htmlElem.ToString(SaveOptions.None);

            StyleCode = GenerateStyleCode();

            HTMLBodyCode = htmlTemplate.
                Replace(CodeConstants.HTML.LineNumbersHolderText, String.Format("{1}<pre>{0}</pre>", GenerateLineNumbers(lineCount), Environment.NewLine)).
                Replace(CodeConstants.HTML.CodeContentHolderText, codeContent);
        }

        /// <summary>
        /// Generates the whole style code. If the styles are not going to be embedded 
        /// the styles related to the themes and highlighting modes are generated here.
        /// </summary>
        private string GenerateStyleCode()
        {
            if (EmbedStyles) return "";

            var sb = new StringBuilder();

            sb.AppendLine("<style>");
            sb.AppendLine("<!--");

            sb.AppendLine(String.Format("/* {0} Automatically Generated Styles */", CodeConstants.Configs.ApplicationName));
            if (core.Themes.SelectedTheme == null)
            {
                sb.AppendLine(String.Format("/* Styles for the {0} built-in default theme */", CodeConstants.Configs.ApplicationName));
            }
            else
            {
                sb.AppendLine(String.Format("/* Styles for the \"{0}\" theme  */", core.Themes.SelectedTheme.Name));
            }

            foreach (var stylePair in dicStyles)
            {
                sb.AppendLine(CreateStyleBlock(CodeConstants.HTML.StyleNamePrefix + stylePair.Key, stylePair.Value));
            }

            if (core.Languages.IsAnyLanguageSelected && core.ColorProfiles.IsAnyProfileSelected)
            {
                sb.AppendLine();
                sb.AppendLine(String.Format("/* Styles for the \"{0}\" highlighting style */", core.ColorProfiles.SelectedProfile.Name));
                sb.AppendLine();

                foreach (var pair in core.ColorProfiles.SelectedProfile.GroupColors)
                {
                    sb.AppendLine(CreateStyleBlock(CodeConstants.HTML.StyleNameCodeColorPrefix + pair.Key, 
                        GenerateStyleStringForSpan(pair.Value.Color, pair.Value.Bold, pair.Value.Italic)));
                }
            }

            sb.AppendLine("-->");
            sb.AppendLine("</style>");

            return sb.ToString();
        }

        /// <summary>
        /// Generates the theme based styles, and fills the dicStyles dictionary.
        /// The keys are main element names, and the values are 
        /// styles that can be not embedded.
        /// </summary>
        private void GenerateThemeBasedStyles()
        {
            Debug.Assert(theme != null);
            dicStyles.Clear();

            string style = "";

            #region CodeContentTD
            //Creating CodeContentTD
            style = CreateStyleString(
                "font-size", theme.MainContentFontSize.ToString() + "pt",
                "background", ColorUtils.ColorTo6CharHtmlString(theme.CodeContentBackColor),
                "color", ColorUtils.ColorTo6CharHtmlString(AppSettings.Settings.DefaultTextColor)
             );
            dicStyles.Add(CodeConstants.HTML.StyleNameCodeContentTD, style);
            #endregion

            #region LineNumbersTD
            // Creating LineNumbersTD
            style = CreateStyleString(
                "font-size", theme.MainContentFontSize.ToString() + "pt",
                "background", ColorUtils.ColorTo6CharHtmlString(theme.LineNumbersBackColor)
            );

            if (theme.LineNumbersFont.Bold)
            {
                style += CreateStyleString(
                    "font-weight", "bold"
                );
            }

            if (theme.LineNumbersFont.Italic)
            {
                style += CreateStyleString(
                    "font-style", "italic"
                );
            }

            if (theme.LineNumbersShowSeperatorLine)
            {
                style += CreateStyleString(
                    "border-right", "1px solid " + ColorUtils.ColorTo6CharHtmlString(theme.LineNumbersSeperatorLineColor)
                );
            }

            dicStyles.Add(CodeConstants.HTML.StyleNameLineNumbersTD, style);

            #endregion

            #region MainContentTable
            // Creating MainContentTable
            style = CreateStyleString(
                "border", "0"
            );

            if (theme.MainContentBorder.Top)
            {
                style += CreateStyleString(
                    "border-top", "1px solid " + ColorUtils.ColorTo6CharHtmlString(theme.MainContentBorder.Color)
                );
            }

            if (theme.MainContentBorder.Bottom)
            {
                style += CreateStyleString(
                    "border-bottom", "1px solid " + ColorUtils.ColorTo6CharHtmlString(theme.MainContentBorder.Color)
                );
            }

            if (theme.MainContentBorder.Left)
            {
                style += CreateStyleString(
                    "border-left", "1px solid " + ColorUtils.ColorTo6CharHtmlString(theme.MainContentBorder.Color)
                );
            }

            if (theme.MainContentBorder.Right)
            {
                style += CreateStyleString(
                    "border-right", "1px solid " + ColorUtils.ColorTo6CharHtmlString(theme.MainContentBorder.Color)
                );
            }

            dicStyles.Add(CodeConstants.HTML.StyleNameMainContentTable, style);

            #endregion

            #region HeaderDiv
            // Generating HeaderTable
            style = CreateStyleString(
                "background", ColorUtils.ColorTo6CharHtmlString(theme.HeaderBackColor),
                "width", "100%",
                "border", "0"
            );

            if (theme.HeaderBorder.Top)
            {
                style += CreateStyleString(
                    "border-top", "1px solid " + ColorUtils.ColorTo6CharHtmlString(theme.HeaderBorder.Color)
                );
            }

            if (theme.HeaderBorder.Bottom)
            {
                style += CreateStyleString(
                    "border-bottom", "1px solid " + ColorUtils.ColorTo6CharHtmlString(theme.HeaderBorder.Color)
                );
            }

            if (theme.HeaderBorder.Left)
            {
                style += CreateStyleString(
                    "border-left", "1px solid " + ColorUtils.ColorTo6CharHtmlString(theme.HeaderBorder.Color)
                );
            }

            if (theme.HeaderBorder.Right)
            {
                style += CreateStyleString(
                    "border-right", "1px solid " + ColorUtils.ColorTo6CharHtmlString(theme.HeaderBorder.Color)
                );
            }

            dicStyles.Add(CodeConstants.HTML.StyleNameHeaderDiv, style);
            #endregion

            #region HeaderTable
            // Generating HeaderTable
            style = CreateStyleString(
                "font-size", theme.HeaderFontSize.ToString() + "pt",
                "width", "100%",
                "color", ColorUtils.ColorTo6CharHtmlString(theme.HeaderFont.Color)

            );

            if (theme.HeaderFont.Bold)
            {
                style += CreateStyleString("font-weight", "bold");
            }

            if (theme.HeaderFont.Italic)
            {
                style += CreateStyleString("font-style", "italic");
            }


            dicStyles.Add(CodeConstants.HTML.StyleNameHeaderTable, style);
            #endregion

            #region CodeDefaultColor
            // Generating HeaderTable
            style = CreateStyleString(
                "color", ColorUtils.ColorTo6CharHtmlString(theme.CodeContentFont.Color)
            );

            if (theme.CodeContentFont.Bold)
            {
                style += CreateStyleString("font-weight", "bold");
            }

            if (theme.CodeContentFont.Italic)
            {
                style += CreateStyleString("font-style", "italic");
            }

            dicStyles.Add(CodeConstants.HTML.StyleNameCodeDefaultColor, style);
            #endregion

        }

        /// <summary>
        /// Generates the line numbers part of the HTML result.
        /// </summary>
        /// <param name="lineCount">The line count.</param>
        private string GenerateLineNumbers(int lineCount)
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 1; i <= lineCount; ++i)
            {
                sb.AppendLine(GenerateEmbeddedStyleSpan(GetLineNumberString(i, lineCount), 
                    theme.LineNumbersFont.Color, false, false));
            }

            return sb.ToString();
        }

        /// <summary>
        /// Gets the string for line each line number,
        /// according to the currently selected theme.
        /// </summary>
        /// <param name="n">The line number to be generated</param>
        /// <param name="maxLines">The maximum line number</param>
        private string GetLineNumberString(int n, int maxLines)
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < theme.LineNumbersSpacesBefore; ++i)
                sb.Append(CodeConstants.HTML.Space);

            int maxDigits = (int)Math.Floor(Math.Log10(maxLines)) + 1;
            int digitNo = (int)Math.Floor(Math.Log10(n)) + 1;

            for (int i = 0; i < maxDigits - digitNo; ++i)
            {
                if (theme.LineNumbersShowZeros)
                    sb.Append("0");
                else
                    sb.Append(CodeConstants.HTML.Space);
            }

            sb.Append(n.ToString());

            if (theme.LineNumbersShowStringAfter)
                sb.Append(theme.LineNumbersStringAfter);

            for (int i = 0; i < theme.LineNumbersSpacesAfter; ++i)
                sb.Append(CodeConstants.HTML.Space);

            return sb.ToString();
        }

        /// <summary>
        /// Gets the XHTML for the body div.
        /// </summary>
        private XElement GetBodyDiv()
        {
            Debug.Assert(theme != null);

            string heightString = "100%";
            if (RunTimeParams.MaxHeight > 0)
                heightString = RunTimeParams.MaxHeight.ToString() + "px";

            string style = CreateStyleString(
                "width", "100%",
                "overflow", "auto",
                "font-family", "Monospace, Courier New, Courier",
                "margin-bottom", "8px",
                "margin-top", "0px"
            );

            if (RunTimeParams.MaxHeight > 0)
            {
                style += CreateStyleString("height", heightString);
            }


            if (EmbedStyles)
            {
                style += dicStyles[CodeConstants.HTML.StyleNameMainContentTable];
            }


            XElement elem = new XElement("div",
                (EmbedStyles ? null :
                    new XAttribute("class", CodeConstants.HTML.StyleNamePrefix + CodeConstants.HTML.StyleNameMainContentTable)
                ),
                (style.Length > 0 ? new XAttribute("style", style) : null),
                GetMainContentTable()
            );
            return elem;
        }

        /// <summary>
        /// Gets the XHTML for the main content table.
        /// </summary>
        /// <returns></returns>
        private XElement GetMainContentTable()
        {
            Debug.Assert(theme != null);

            string mainTableStyleString = CreateStyleString(
                "max-width", "100%",
                "width", "100%"
            );

            XElement elem = new XElement("table",
                new XAttribute("width", "100%"),
                new XAttribute("cellspacing", "0"),
                new XAttribute("cellpadding", "0"),
                (mainTableStyleString.Length > 0 ? new XAttribute("style", mainTableStyleString) : null), 
                new XElement("tr",
                    (this.AddLineNo ? GetLineNumbersTD() : null),
                    GetCodeContentTD()
                )
            );
            return elem;
        }

        /// <summary>
        /// Gets the XHTML for the code content TD (Table Data).
        /// </summary>
        /// <returns></returns>
        private XElement GetCodeContentTD()
        {
            Debug.Assert(theme != null);

            string tdStyle = "";

            tdStyle = CreateStyleString(
                "padding-top", "5px",
                "padding-left", "5px",
                "padding-right", "5px"
            );


            if (EmbedStyles)
            {
                tdStyle += dicStyles[CodeConstants.HTML.StyleNameCodeContentTD];
            }

            XElement elem = new XElement("td",
                (EmbedStyles ? null :
                    new XAttribute("class", CodeConstants.HTML.StyleNamePrefix + CodeConstants.HTML.StyleNameCodeContentTD)
                ),
                new XAttribute("colspan", "2"),
                (tdStyle.Length > 0 ? new XAttribute("style", tdStyle) : null),
                CodeConstants.HTML.CodeContentHolderText
            );

            return elem;
        }

        /// <summary>
        /// Gets the XHTML for the line numbers TD (Table Data).
        /// </summary>
        /// <returns></returns>
        private XElement GetLineNumbersTD()
        {
            Debug.Assert(theme != null);

            int digitCount = linesDigitCount +
                theme.LineNumbersSpacesBefore +
                theme.LineNumbersSpacesAfter +
                (theme.LineNumbersShowStringAfter ? theme.LineNumbersStringAfter.Length : 0);

            // TODO: add linenumberfont
            string style = CreateStyleString(
                "width", (digitCount * GetCharLengthEstimate()).ToString() + "px",
                "padding-top", "5px",
                "padding-left", "5px",
                "padding-right", "5px",
                "text-align", "right"
            );

            if (EmbedStyles)
            {
                style += dicStyles[CodeConstants.HTML.StyleNameLineNumbersTD];
            }

            XElement elem = new XElement("td",
                (EmbedStyles ? null :
                    new XAttribute("class", CodeConstants.HTML.StyleNamePrefix + CodeConstants.HTML.StyleNameLineNumbersTD)
                ),
                new XAttribute("style", style),
                CodeConstants.HTML.LineNumbersHolderText
            );

            return elem;
        }

        /// <summary>
        /// Gets the XHTML for the header div.
        /// </summary>
        /// <returns></returns>
        private XElement GetHeaderDiv()
        {
            Debug.Assert(theme != null);

            string style = "";

            if (EmbedStyles)
            {
                style += dicStyles[CodeConstants.HTML.StyleNameHeaderDiv];
            }

            XElement elem = new XElement("div",
                (EmbedStyles ? null :
                    new XAttribute("class", CodeConstants.HTML.StyleNamePrefix + CodeConstants.HTML.StyleNameHeaderDiv)
                ),
                (style.Length > 0 ? new XAttribute("style", style) : null),
                GetHeaderTable()
            );

            return elem;
        }

        /// <summary>
        /// Gets the XHTML for the header table.
        /// </summary>
        /// <returns></returns>
        private XElement GetHeaderTable()
        {
            Debug.Assert(theme != null);

            string style = "";

            if (EmbedStyles)
            {
                style += dicStyles[CodeConstants.HTML.StyleNameHeaderTable];
            }

            string StyleForTh = CreateStyleString(
                "text-align", "left",
                "padding-left", "5px",
                "padding-right", "5px"
            );

            XElement elem = new XElement("table",
                (EmbedStyles ? null :
                    new XAttribute("class", CodeConstants.HTML.StyleNamePrefix + CodeConstants.HTML.StyleNameHeaderTable)
                ),
                new XAttribute("cellspacing", "0"),
                new XAttribute("cellpadding", "0"),
                (style.Length > 0 ? new XAttribute("style", style) : null),
                new XElement("tr",
                    new XElement("th",
                        new XAttribute("style", StyleForTh),
                        this.HeaderCaption
                    )
                )
            );

            return elem;
        }

        /// <summary>
        /// Gets the char length estimate, according to the font-size in the currently selected theme.
        /// </summary>
        /// <returns></returns>
        private int GetCharLengthEstimate()
        {
            return theme.MainContentFontSize;
        }

        #endregion

        #region Public Static Utility Methods

        /// <summary>
        /// Creates a style block. This will be in the form of:
        /// <code>
        /// .TheName
        /// {
        ///    // style items here
        /// }
        /// </code>
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="styles">The string of semi-colon seperated styles.</param>
        public static string CreateStyleBlock(string name, string styles)
        {
            StringBuilder sb = new StringBuilder();

            sb.Append(String.Format(".{0} {{{1}    ", name, Environment.NewLine));

            int lastSemiColon = styles.LastIndexOf(';');
            if (lastSemiColon > 0)
            {
                string preString = styles.Substring(0, lastSemiColon);
                preString = preString.Replace(";", ";" + Environment.NewLine + "    ");
                sb.Append(preString + styles.Substring(lastSemiColon));
            }
            else
            {
                sb.Append(styles);
            }

            sb.AppendLine();
            sb.AppendLine("}");

            return sb.ToString();
        }

        /// <summary>
        /// Creates a style string. i.e. the semicolon seperated style items. 
        /// Counting from zero, the even indexed strings are properties, 
        /// and odd-indexed strings are the associated values.
        /// </summary>
        /// <param name="styles">The style stirngs</param>
        public static string CreateStyleString(params string[] styles)
        {
            if (styles.Length <= 0) return "";

            StringBuilder sb = new StringBuilder();
            
            for (int i = 0; i < styles.Length; ++i)
            {
                if (i % 2 == 0)
                    sb.Append(styles[i]);
                else
                    sb.AppendFormat(":{0};", styles[i]);
            }

            if (styles.Length % 2 == 1)
                sb.Append(";");

            return sb.ToString();
        }

        /// <summary>
        /// Generates the style string for span, that has a color, 
        /// and can optionally be bold, or italic.
        /// </summary>
        /// <param name="color">The color.</param>
        /// <param name="isBold">if set to <c>true</c> adds the bold style.</param>
        /// <param name="isItalic">if set to <c>true</c> adds the italic style.</param>
        public static string GenerateStyleStringForSpan(Color color, bool isBold, bool isItalic)
        {
            string style = CreateStyleString("color", ColorUtils.ColorTo6CharHtmlString(color));

            if (isBold)
            {
                style += CreateStyleString(
                    "font-weight", "bold"
                );
            }

            if (isItalic)
            {
                style += CreateStyleString(
                    "font-style", "italic"
                );
            }

            return style;
        }

        /// <summary>
        /// Generates an HTML span with embedded styles for color, and optional styles for bold and italic.
        /// </summary>
        /// <param name="content">The content.</param>
        /// <param name="color">The color.</param>
        /// <param name="isBold">if set to <c>true</c> adds the bold style.</param>
        /// <param name="isItalic">if set to <c>true</c> adds the italic style.</param>
        /// <returns></returns>
        public static string GenerateEmbeddedStyleSpan(string content, Color color, bool isBold, bool isItalic)
        {
            XElement elem = new XElement("span",
                new XAttribute("style", GenerateStyleStringForSpan(color, isBold, isItalic)),
                content);

            return elem.ToString(SaveOptions.DisableFormatting);
        }

        /// <summary>
        /// Generates an HTML span with a reference to a class of styles. i.e. with not embedded styles
        /// </summary>
        /// <param name="content">The content.</param>
        /// <param name="className">Name of the style class.</param>
        public static string GenerateNotEmbeddedStyleSpan(string content, string className)
        {
            XElement elem = new XElement("span",
                new XAttribute("class", className),
                content);

            return elem.ToString(SaveOptions.DisableFormatting);
        }

        #endregion

    }
}
